const ROW = 4,
  W = 125,
  SPACE = 20,
  SIDE_W = ROW * (W + SPACE) + SPACE,
  VALUES = [
    '2',
    '4',
    '8',
    '16',
    '32',
    '64',
    '128',
    '256',
    '512',
    '1024',
    '2048'
  ],
  VALUE_COLORS = [
    '#EEE4DA',
    '#EDE0C8',
    '#EDA166',
    '#F08151',
    '#F1654D',
    '#F1462E',
    '#E8C65F',
    '#E8C34F',
    '#E8BE40',
    '#E8BB31',
    '#E8B724'
  ],
  FONT_SIZE = [70, 70, 70, 60, 60, 60, 50, 50, 50, 50, 40]

let maxVal = 0
const isWin = () => maxVal === VALUES.length - 1
const isLose = () =>
  !data.some((row, i) =>
    row.some(
      (val, j) => val === -1 || val === row[j + 1] || val === data[i + 1]?.[j]
    )
  )

let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')

canvas.width = canvas.height = SIDE_W
canvas.style.cssText = 'position: absolute;  inset: 0; margin: auto;'
document.body.appendChild(canvas)
document.body.style.backgroundColor = '#F9F7EB'

let data = Array.from({ length: ROW }, () => Array(ROW).fill(-1))

const trans = pos => pos * (W + SPACE) + SPACE

const convert = arr =>
  arr[0].map((_, colIndex) => arr.map(col => col[colIndex]))

const toUp = data => data.map(row => move(row.reverse()).reverse())

const toDown = data => data.map(row => move(row))

const toLeft = data =>
  convert(
    convert(data).map((col, index) => move(col.reverse(), index).reverse())
  )
const toRight = data =>
  convert(convert(data).map((col, index) => move(col, index)))

const keydownEvent = {
  ArrowUp: toUp,
  ArrowDown: toDown,
  ArrowLeft: toLeft,
  ArrowRight: toRight
}

window.addEventListener('keydown', e => {
  const cb = keydownEvent[e.key]
  if (!cb) return

  const newData = cb(structuredClone(data))

  if (JSON.stringify(newData) === JSON.stringify(data)) return
  data = newData
  update()
})

const update = () => {
  ctx.clearRect(0, 0, SIDE_W, SIDE_W)
  drawBoard()
  drawAllDataBlock()

  if (isWin()) {
    setTimeout(() => {
      alert('你赢了！')
    }, 0)
    return
  }
  genRandomeBlock()

  if (isLose()) {
    setTimeout(() => {
      alert('你输了！')
    }, 0)
  }
}

const move = list => {
  let temp = [],
    canMerge = false,
    len = list.length

  for (let i = len - 1; i >= 0; i--) {
    if (list[i] === -1) continue
    if (canMerge && list[i] === temp[0]) {
      maxVal = Math.max(maxVal, ++temp[0])
      canMerge = false
    } else {
      temp.unshift(list[i])
      canMerge = true
    }
  }

  return [...Array(len - temp.length).fill(-1), ...temp]
}

const drawAllDataBlock = () => {
  data.forEach((row, i) => {
    row.forEach((val, j) => {
      if (val !== -1) drawDataBlock(i, j, val)
    })
  })
}

const drawDataBlock = (x, y, valIndex, w = W) => {
  let [dataVal, fillColor, fontSize] = [
    VALUES[valIndex],
    VALUE_COLORS[valIndex],
    FONT_SIZE[valIndex]
  ]

  drawBlock(x, y, fillColor)
  ctx.font = `${fontSize}px serif`
  ctx.textAlign = 'center'
  ctx.textBaseline = 'middle'
  ctx.fillStyle = valIndex < 2 ? '#776E65' : '#F9F6F2'
  ctx.fillText(dataVal, trans(x) + w / 2, trans(y) + w / 2)
}

const drawBlock = (x, y, fillColor = '#C2B4A5', w = W) => {
  ctx.fillStyle = fillColor
  ctx.fillRect(trans(x), trans(y), w, w)
}

const drawBoard = () => {
  ctx.fillStyle = '#AD9D8F'
  ctx.fillRect(0, 0, SIDE_W, SIDE_W)

  let i = 0
  while (i < ROW) {
    let j = 0
    while (j < ROW) {
      drawBlock(i, j)
      j++
    }
    i++
  }
}

const getRandomFreePos = () => {
  let freeArr = data.reduce((acc, row, i) => {
    row.forEach((val, j) => {
      if (val === -1) acc.push([i, j])
    })
    return acc
  }, [])
  return freeArr[Math.floor(Math.random() * freeArr.length)]
}

const genRandomeBlock = () => {
  const [x, y] = getRandomFreePos()
  let val = Math.random() < 0.5 ? 0 : 1
  data[x][y] = val
  drawDataBlock(x, y, val)
}

const start = () => {
  drawBoard()

  for (let i = 0; i < 2; i++) {
    genRandomeBlock()
  }
}

window.onload = start
